package calculationsandtests;


/**
 * Calculates the degrees of the lines based on the latitude, longitude and date given.
 * 
 * 
 * @author Nick Davis
 * March 2013
 */
public class SundialCalc {

	private double gnomonDeg = 0.0;
	
	private int hour;
	private int minute;
	private int day;
	private String time;
	private double elevenOne;
	private double tenTwo;
	private double nineThree;
	private double eightFour;
	private double sevenFive;
	private String AMPM;
	private String minuteStr;
	
	public SundialCalc(){
	}
	
	/**
	 * Calculates the location of the given longitude and latitude.
	 * 
	 * @param longDeg - just the degrees of the longitude entered by the user. 
	 * @param longMin - just the minutes of the longitude entered by the user.
	 * @param latDeg  - just the degrees of the latitude entered by the user.
	 * @param latMin  - just the minutes of the latitude entered by the user.
	 * @param month   - the month entered by the user.
	 * @param day	  - the day entered by the user.
	 * @param year	  - the year entered by the user.
	 */
	public void location(double longDeg, double latDeg, int month, int day, int year, boolean dayLightSavings){
		boolean validDate;
		boolean validLongLat;
		validDate = isValidDate(month, day, year);
		validLongLat = isValidLongLat(longDeg, latDeg);
		
		if(validDate && validLongLat){
		
			this.setGnomon(latDeg);
			this.setDay(month, day);
			this.setTime(longDeg, dayLightSavings);
			this.setAngles(latDeg);
		
		}else{
			if(!validDate){
				System.out.println("The date entered is not a valid date.");
			}
			if(!validLongLat){
				System.out.println("The longitude and latitude entered is not valid.");
			}
			System.out.println("Please Try Again.");
		}
	}
	
	/**
	 * Gets the time at noon.
	 * 
	 * @return - The time to put at the top of the drawing.
	 */
	public String getNoon(){
		return time;
	}
	
	public void setTime(double longDeg, boolean dayLightSavings){
		double offset = 9.87 * Math.sin(this.degreeToRad(2 * 360 * (this.getDay() - 81) / 365.0));
		offset -= 7.53 * Math.cos(this.degreeToRad(360 * (this.getDay() - 81) / 365.0));
		offset -= 1.5 * Math.sin(this.degreeToRad(360 * (this.getDay() - 81) / 365.0));

		minute = (int)((longDeg % 15) * 4 - offset);
		
		//Adjusts for time zones to the east of 0 degrees longitude.
		if(longDeg < 0){
			minute = minute + 60;
		}
		
		
		if(minute < 10 && minute > -10){
			minuteStr = "0" + Math.abs(minute);
		}else{
			minuteStr = "" + Math.abs(minute);
		}
		
		//Adjusts for daylight savings time.
		if(dayLightSavings){
			if((int)longDeg % 15 < 7.5){
				this.hour = 1;
			}else{
				this.hour = 12;
			}
		}else{
			if((int) longDeg % 15 < 7.5){
				this.hour = 12;
			}else{
				this.hour = 11;
			}
		}
		
		this.time = this.hour + ":" + minuteStr;
		
		if(this.hour == 11){
			this.setAMPM("5:" + minuteStr);
		}else if(this.hour == 12){
			this.setAMPM("6:" + minuteStr);
		}else{
			this.setAMPM("7:" + minuteStr);
		}
	}
	
	/**
	 * Gets the AMPM time.
	 * 
	 * @return - The AMPM times.
	 */
	public String getAMPM(){
		return AMPM;
	}
	
	public String getAMPMAdjusted(int adjustment)
	{
		try
		{
			String[] timeArray = this.AMPM.split(":");
			int newHour = Integer.parseInt(timeArray[0]) + adjustment;
			return (Integer.toString(newHour) + ":" + timeArray[1]);
		}
		catch (NullPointerException e)
		{
			System.out.println(e);
		}
		return "";
	}

	/**
	 * Sets the AMPM times.
	 * 
	 * @param AMPM - The time for AMPM
	 */
	private void setAMPM(String AMPM){
		this.AMPM = AMPM;
	}
	
	/**
	 * Sets the angle of the lines.
	 * 
	 * @param latDeg - The degree the user entered for the latitude.
	 */
	private void setAngles(double latDeg){
		//Calculates the angle of the time lines.
		this.elevenOne	= this.radianToDeg(Math.atan(Math.tan(degreeToRad(15)) * Math.sin(degreeToRad(latDeg))));
		this.tenTwo 	= this.radianToDeg(Math.atan(Math.tan(degreeToRad(30)) * Math.sin(degreeToRad(latDeg))));
		this.nineThree 	= this.radianToDeg(Math.atan(Math.tan(degreeToRad(45)) * Math.sin(degreeToRad(latDeg))));
		this.eightFour 	= this.radianToDeg(Math.atan(Math.tan(degreeToRad(60)) * Math.sin(degreeToRad(latDeg))));
		this.sevenFive 	= this.radianToDeg(Math.atan(Math.tan(degreeToRad(75)) * Math.sin(degreeToRad(latDeg))));
	}
	
	/**
	 * Gets the angle of the lines for seven and five.
	 * 
	 * @return - The angle the line is supposed to be for seven and four.
	 */
	public double getSevenFive(){
		return sevenFive;
	}
	
	/**
	 * Gets the angle of the lines for eight and four.
	 * 
	 * @return - The angle the line is supposed to be for eight and four.
	 */
	public double getEightFour(){
		return eightFour;
	}
	
	/**
	 * Gets the angle of the lines for ten and two.
	 * 
	 * @return - The angle the line is supposed to be for nine and three.
	 */
	public double getTenTwo(){
		return tenTwo;
	}
	
	/**
	 * Gets the angle of the lines for nine and three.
	 * 
	 * @return - The angle the line is supposed to be for nine and three.
	 */
	public double getNineThree(){
		return nineThree;
	}
	
	/**
	 * Gets the angle of the lines for eleven and one.
	 * 
	 * @return - The angle the line is supposed to be for eleven and one.
	 */
	public double getElevenOne(){
		return elevenOne;
	}
	
	/**
	 * Gets the gnomon degree.
	 * 
	 * @return - The angle of the gnomon.
	 */
	public double getGnomon(){
		return gnomonDeg;
	}
	
	/**
	 * Sets the degree of the gnomon.
	 * 
	 * @param gnomon - The latitude the user entered.
	 */
	private void setGnomon(double gnomon){
		this.gnomonDeg = Math.abs(gnomon);
	}
	
	/**
	 * Gives the day of the year calculated by setDay.
	 * 
	 * @return The day of the year.
	 */
	private int getDay(){
		return this.day;
	}
	
	/**
	 * Calculates the day of the year and sets it to a 1-365 scale.
	 * 
	 * @param month	- The month the user entered.
	 * @param day	- The day of the year the user entered.
	 */
	private void setDay(int month, int day){
		int numOfDays = day;
		
		if(month > 1){
			numOfDays += 31;
		}
		if(month > 2){
			numOfDays += 28;
		}
		if(month > 3){
			numOfDays += 31;
		}
		if(month > 4){
			numOfDays += 30;
		}
		if(month > 5){
			numOfDays += 31;
		}
		if(month > 6){
			numOfDays += 30;
		}
		if(month > 7){
			numOfDays += 31;
		}
		if(month > 8){
			numOfDays += 31;
		}
		if(month > 9){
			numOfDays += 30;
		}
		if(month > 10){
			numOfDays += 31;
		}
		if(month > 11){
			numOfDays += 30;
		}
		this.day = numOfDays;
	}
	
	/**
	 * Converts the radian value to degrees.
	 * 
	 * @param rad - The radian to turn into degrees.
	 * 
	 * @return The degree value of the radian.
	 */
	private double radianToDeg(double rad){
		return(rad * 180 / Math.PI);
	}
	
	/**
	 * Converts the degree value to radians.
	 * 
	 * @param deg - The degree to turn into radians.
	 * 
	 * @return The radian value of the degree.
	 */
	private double degreeToRad(double deg){
		return(deg * Math.PI / 180);
	}
	
	/**
	 * Checks to make sure the user entered a valid date.
	 * 
	 * @param 	month - the month of the year.
	 * @param 	day   - the day of the year.
	 * @param 	year  - the year.
	 * 
	 * @return 	true if the date is valid.
	 * 			false if the date is invalid.
	 */
	private boolean isValidDate(int month, int day, int year){
		
		//Checks to make sure the month is valid.
		if(month < 0 || month > 13){
			return false;
		}
				
		//Checks to make sure the day is valid.
		if(day < 1 || day > 31){
			return false;
		}
				
		//Checks the months with 30 days.
		if((month == 4 || month == 6 || month == 9 || month == 11) && day == 31){
			return false;
		}
				
		//Checks to see if February is a leap year and corrects it if the person entered a leap year day.
		if(year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) && month == 2 && day == 29){
			return true;
		}
				
		//Checks the month of February, makes false if day is over 28.
		if(month == 2 && day > 28){
			return false;
		}
				
		return true;
	}
	
	/**
	 * Checks the longitude and latitude to make sure they're valid values.
	 * 
	 * @param longDeg	- The value the user entered for longitude.
	 * @param latDeg	- The value the user entered for latitude.
	 * @return - true if the date is valid.
	 * 			 false if the date is invalid.
	 */
	private boolean isValidLongLat(double longDeg, double latDeg){
		
		if(longDeg > 180 || longDeg < -180){
			return false;
		}
		
		if(latDeg > 90 && latDeg < -90){
			return false;
		}
		
		return true;
	}
}
